library(leaflet)
library(tidyverse)
library(readxl)
library(lubridate)
library(kableExtra)
library(GGally)
library(corrplot)
library(ggplot2)
library(dplyr)
library(gridExtra)
library(osmdata)
library(sf)
library(ggmap)
library(MASS)Iflow
Librerías
Cargar Datos
df <- read_excel("iFlowDatos.xlsx")
df# A tibble: 27,484 × 16
iddomicilioorden direccion localidad InicioHorario1 FinHorario1 latitud
<dbl> <chr> <chr> <dbl> <dbl> <dbl>
1 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
2 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
3 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
4 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
5 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
6 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
7 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
8 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
9 74958 M PEDRAZA 2370 CAPITAL 901 1401 -34.6
10 74958 M PEDRAZA 2370 CAPITAL 901 1401 -34.6
# ℹ 27,474 more rows
# ℹ 10 more variables: longitud <dbl>, cliente <dbl>, mes <dbl>, Bultos <dbl>,
# Peso <dbl>, Unidades <dbl>, InicioVisitaPlanificado <chr>,
# FinVisitaPlanificado <chr>, InicioVisitaReal <chr>, FinVisitaReal <chr>
Limpieza de los Datos
df_clean <- df %>%
filter(latitud<(-34),longitud<(-58)) %>%
mutate(inicio = parse_date_time(InicioVisitaReal, orders = c("ymd HMS", "ymd_HMS", "dmy HMS"), tz = Sys.timezone(), quiet = TRUE)) %>% na.omit()%>%
mutate(cliente = as.factor(cliente))
names(df_clean) <- tolower(names(df_clean))
df_clean# A tibble: 27,382 × 17
iddomicilioorden direccion localidad iniciohorario1 finhorario1 latitud
<dbl> <chr> <chr> <dbl> <dbl> <dbl>
1 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
2 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
3 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
4 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
5 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
6 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
7 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
8 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
9 74958 M PEDRAZA 2370 CAPITAL 901 1401 -34.6
10 74958 M PEDRAZA 2370 CAPITAL 901 1401 -34.6
# ℹ 27,372 more rows
# ℹ 11 more variables: longitud <dbl>, cliente <fct>, mes <dbl>, bultos <dbl>,
# peso <dbl>, unidades <dbl>, iniciovisitaplanificado <chr>,
# finvisitaplanificado <chr>, iniciovisitareal <chr>, finvisitareal <chr>,
# inicio <dttm>
colSums(is.na(df_clean)) iddomicilioorden direccion localidad
0 0 0
iniciohorario1 finhorario1 latitud
0 0 0
longitud cliente mes
0 0 0
bultos peso unidades
0 0 0
iniciovisitaplanificado finvisitaplanificado iniciovisitareal
0 0 0
finvisitareal inicio
0 0
dim(df_clean)[1] 27382 17
Análisis de Datos Básico
summary(df_clean) iddomicilioorden direccion localidad iniciohorario1
Min. : 74956 Length:27382 Length:27382 Min. : 0
1st Qu.: 77460 Class :character Class :character 1st Qu.:901
Median : 82348 Mode :character Mode :character Median :901
Mean :101622 Mean :901
3rd Qu.:121664 3rd Qu.:901
Max. :183277 Max. :901
finhorario1 latitud longitud cliente mes
Min. :1400 Min. :-34.85 Min. :-58.73 20:16510 Min. :5.000
1st Qu.:1401 1st Qu.:-34.62 1st Qu.:-58.48 70:10872 1st Qu.:5.000
Median :1401 Median :-34.60 Median :-58.44 Median :6.000
Mean :1401 Mean :-34.60 Mean :-58.44 Mean :6.021
3rd Qu.:1401 3rd Qu.:-34.58 3rd Qu.:-58.40 3rd Qu.:7.000
Max. :2359 Max. :-34.39 Max. :-58.15 Max. :7.000
bultos peso unidades iniciovisitaplanificado
Min. : 0.100 Min. : 0.00 Min. : 1.00 Length:27382
1st Qu.: 2.000 1st Qu.: 13.00 1st Qu.: 2.00 Class :character
Median : 3.000 Median : 20.94 Median : 6.00 Mode :character
Mean : 5.692 Mean : 40.95 Mean : 28.37
3rd Qu.: 6.000 3rd Qu.: 39.00 3rd Qu.: 40.00
Max. :360.000 Max. :2475.00 Max. :2203.00
finvisitaplanificado iniciovisitareal finvisitareal
Length:27382 Length:27382 Length:27382
Class :character Class :character Class :character
Mode :character Mode :character Mode :character
inicio
Min. :2024-05-03 07:17:51.00
1st Qu.:2024-05-24 15:38:00.00
Median :2024-06-18 10:34:53.00
Mean :2024-06-17 22:15:43.75
3rd Qu.:2024-07-11 11:21:32.00
Max. :2024-08-06 16:57:00.00
Distintas Variables
colnames(df_clean) [1] "iddomicilioorden" "direccion"
[3] "localidad" "iniciohorario1"
[5] "finhorario1" "latitud"
[7] "longitud" "cliente"
[9] "mes" "bultos"
[11] "peso" "unidades"
[13] "iniciovisitaplanificado" "finvisitaplanificado"
[15] "iniciovisitareal" "finvisitareal"
[17] "inicio"
sapply(df_clean, class)$iddomicilioorden
[1] "numeric"
$direccion
[1] "character"
$localidad
[1] "character"
$iniciohorario1
[1] "numeric"
$finhorario1
[1] "numeric"
$latitud
[1] "numeric"
$longitud
[1] "numeric"
$cliente
[1] "factor"
$mes
[1] "numeric"
$bultos
[1] "numeric"
$peso
[1] "numeric"
$unidades
[1] "numeric"
$iniciovisitaplanificado
[1] "character"
$finvisitaplanificado
[1] "character"
$iniciovisitareal
[1] "character"
$finvisitareal
[1] "character"
$inicio
[1] "POSIXct" "POSIXt"
Cambiar las variables, para que las que tengan que estar en formato fecha, lo estén
df_clean$iniciovisitaplanificado <- as.POSIXct(df_clean$iniciovisitaplanificado, format = "%Y-%m-%d %H:%M:%OS", tz = "UTC")
df_clean$finvisitaplanificado <- as.POSIXct(df_clean$finvisitaplanificado, format = "%Y-%m-%d %H:%M:%OS", tz = "UTC")
df_clean$iniciovisitareal <- as.POSIXct(df_clean$iniciovisitareal, format = "%Y-%m-%d %H:%M:%OS", tz = "UTC")
df_clean$finvisitareal <- as.POSIXct(df_clean$finvisitareal, format = "%Y-%m-%d %H:%M:%OS", tz = "UTC")
df_clean# A tibble: 27,382 × 17
iddomicilioorden direccion localidad iniciohorario1 finhorario1 latitud
<dbl> <chr> <chr> <dbl> <dbl> <dbl>
1 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
2 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
3 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
4 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
5 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
6 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
7 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
8 74956 VIDAL 2044 CAPITAL 901 1401 -34.6
9 74958 M PEDRAZA 2370 CAPITAL 901 1401 -34.6
10 74958 M PEDRAZA 2370 CAPITAL 901 1401 -34.6
# ℹ 27,372 more rows
# ℹ 11 more variables: longitud <dbl>, cliente <fct>, mes <dbl>, bultos <dbl>,
# peso <dbl>, unidades <dbl>, iniciovisitaplanificado <dttm>,
# finvisitaplanificado <dttm>, iniciovisitareal <dttm>, finvisitareal <dttm>,
# inicio <dttm>
colnames(df_clean) [1] "iddomicilioorden" "direccion"
[3] "localidad" "iniciohorario1"
[5] "finhorario1" "latitud"
[7] "longitud" "cliente"
[9] "mes" "bultos"
[11] "peso" "unidades"
[13] "iniciovisitaplanificado" "finvisitaplanificado"
[15] "iniciovisitareal" "finvisitareal"
[17] "inicio"
sapply(df_clean, class)$iddomicilioorden
[1] "numeric"
$direccion
[1] "character"
$localidad
[1] "character"
$iniciohorario1
[1] "numeric"
$finhorario1
[1] "numeric"
$latitud
[1] "numeric"
$longitud
[1] "numeric"
$cliente
[1] "factor"
$mes
[1] "numeric"
$bultos
[1] "numeric"
$peso
[1] "numeric"
$unidades
[1] "numeric"
$iniciovisitaplanificado
[1] "POSIXct" "POSIXt"
$finvisitaplanificado
[1] "POSIXct" "POSIXt"
$iniciovisitareal
[1] "POSIXct" "POSIXt"
$finvisitareal
[1] "POSIXct" "POSIXt"
$inicio
[1] "POSIXct" "POSIXt"
Análisis
df_clean <- df_clean %>%
mutate(TiempoEntrega = finvisitareal - iniciovisitareal)Cantidad de entregas por localidad
df_counts <- df_clean %>%
group_by(localidad) %>%
summarise(conteo = n())%>%
arrange(desc(conteo))%>%
head(5)
ggplot(df_counts, aes(x = localidad, y =conteo)) +
geom_bar(stat = "identity") 
Calculamos el tiempo promedio de demora en la salida, y en la llegada
calcular_tiempo_formateado <- function(inicio, fin) {
# Calcula la diferencia en minutos con decimales
tiempo_minutos_dec <- as.numeric(difftime(fin, inicio, units = "mins"))
# Convierte el resultado a minutos enteros, ignorando los segundos
tiempo_minutos <- floor(tiempo_minutos_dec)
# Retorna el valor numérico
return(tiempo_minutos)
}
# Aplicar la función en el data frame sin crear columnas adicionales
df_clean <- df_clean %>%
mutate(DemoraSalida = calcular_tiempo_formateado(iniciovisitaplanificado, iniciovisitareal))%>%
mutate(DemoraLlegada = calcular_tiempo_formateado(finvisitaplanificado, finvisitareal))
promedio_demora_salida <- mean(df_clean$DemoraSalida,na.rm = TRUE)
promedio_demora_salida[1] 133.482
promedio_demora_llegada <- mean(df_clean$DemoraLlegada,na.rm=TRUE)
promedio_demora_llegada[1] 139.4571
Con lo calculado arriba, sacamos el tiempo promedio de demora en la salida y llegada, en las distintas localidades y graficamos
df_demora_por_localidad <- df_clean%>%
group_by(localidad) %>%
summarise(demora_salida_localidad=mean(DemoraSalida,na.rm = T),demora_llegada_localidad=mean(DemoraLlegada,na.rm = T)) %>%
arrange(demora_salida_localidad) %>%
filter(demora_salida_localidad < 3000) %>%
mutate(localidad = factor(localidad, levels = localidad))
ggplot(df_demora_por_localidad,aes(x=localidad,y=demora_salida_localidad)) +
geom_bar(stat="identity") +
labs(title = "Demora de Salida por Localidad",
x = "Localidad",
y = "Demora de Salida (promedio)") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplot(df_demora_por_localidad,aes(x=localidad,y=demora_llegada_localidad))+
geom_bar(stat="identity")+
labs(title = "Demora de Salida por Localidad",
x = "Localidad",
y = "Demora de Salida (promedio)") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Ahora calculamos la demora promedio por cliente
demora_promedio_por_cliente <- df_clean%>%
group_by(cliente) %>%
summarise(demora_salida_cliente=mean(DemoraSalida,na.rm=T),demora_llegada_cliente=mean(DemoraLlegada,na.rm=T))
demora_melt <- reshape2::melt(demora_promedio_por_cliente, id.vars = 'cliente')
# Crear el gráfico con ggplot2
ggplot(demora_melt, aes(x = factor(cliente), y = value, fill = variable)) +
geom_bar(stat = 'identity', position = 'dodge') +
labs(x = 'Cliente', y = 'Demora', fill = 'Tipo de demora') +
theme_minimal()
Distribución de las entregas en un mapa
library(dplyr)
library(sf)
library(plotly)
Attaching package: 'plotly'
The following object is masked from 'package:MASS':
select
The following object is masked from 'package:ggmap':
wind
The following object is masked from 'package:ggplot2':
last_plot
The following object is masked from 'package:stats':
filter
The following object is masked from 'package:graphics':
layout
Sys.setenv("MAPBOX_TOKEN" = "pk.eyJ1IjoiYWd1c3Rpbm9ydWUiLCJhIjoiY20yamR1dmlhMDRnMjJscHgxZTk4a3Z1OSJ9.3XpQh1Kfpitza34209j9vA")
df.sf <- df %>%
filter(!is.na(longitud) & !is.na(latitud)) %>%
filter(longitud < 0) %>%
filter(latitud < -34.0) %>%
st_as_sf(coords = c("longitud", "latitud"), crs = 4326)
coords <- st_coordinates(df.sf)
plot_ly() %>%
add_trace(
type = 'scattermapbox',
mode = 'markers',
lon = coords[, 1], # Longitud
lat = coords[, 2], # Latitud
marker = list(size = 8, color = 'blue') # Tamaño y color de los puntos
) %>%
layout(
mapbox = list(
style = 'streets',
zoom = 8, # Ajusta el nivel de zoom según tu preferencia
center = list(lon = mean(coords[, 1]), lat = mean(coords[, 2]))
),
showlegend = FALSE # Ocultar leyenda si no es necesaria
) %>%
config(mapboxAccessToken = Sys.getenv("MAPBOX_TOKEN")) Distribucion de las entregas de los distintos clientes
library(dplyr)
library(sf)
library(plotly)
# Configurar el token de Mapbox
Sys.setenv("MAPBOX_TOKEN" = "pk.eyJ1IjoiYWd1c3Rpbm9ydWUiLCJhIjoiY20yamR1dmlhMDRnMjJscHgxZTk4a3Z1OSJ9.3XpQh1Kfpitza34209j9vA")
# Filtrar y convertir los datos a objeto sf
df.sf <- df %>%
filter(!is.na(longitud) & !is.na(latitud)) %>%
filter(longitud < 0) %>%
filter(latitud < -34.0) %>%
st_as_sf(coords = c("longitud", "latitud"), crs = 4326)
# Extraer las coordenadas
coords <- st_coordinates(df.sf)
# Asignar etiquetas para la leyenda
df.sf$label <- case_when(
df.sf$cliente == 20 ~ 'Cliente 20', # Etiqueta para cliente 20
df.sf$cliente == 70 ~ 'Cliente 70', # Etiqueta para cliente 70
TRUE ~ 'Otros Clientes' # Otros clientes
)
# Asignar colores basados en la etiqueta
df.sf$color <- case_when(
df.sf$cliente == 20 ~ 'purple', # Color violeta para cliente 20
df.sf$cliente == 70 ~ 'yellow', # Color amarillo para cliente 70
TRUE ~ 'black' # Color negro para los demás
)
# Crear el gráfico con leyenda y colores personalizados
plot_ly() %>%
add_trace(
type = 'scattermapbox',
mode = 'markers',
lon = coords[, 1], # Longitud
lat = coords[, 2], # Latitud
text = df.sf$cliente, # Mostrar el cliente al pasar el mouse
marker = list(
size = 8,
color = df.sf$color # Color personalizado
),
showlegend = TRUE,
name = df.sf$label # Asignar la etiqueta para la leyenda
) %>%
layout(
mapbox = list(
style = 'streets',
zoom = 8, # Ajustar nivel de zoom
center = list(lon = mean(coords[, 1]), lat = mean(coords[, 2])) # Centrar en las coordenadas promedio
),
showlegend = TRUE # Mostrar la leyenda
) %>%
config(mapboxAccessToken = Sys.getenv("MAPBOX_TOKEN"))Peso por unidad
peso_por_unidad <- function(peso, unidades) {
if (unidades == 0) {
return(NA) # Si las unidades son 0, devolvemos NA para evitar la división por 0
} else {
return(peso / unidades)
}
}
df$peso_por_unidad <- mapply(peso_por_unidad, df$Peso, df$Unidades)
head(df[c("Peso", "Unidades", "peso_por_unidad")])# A tibble: 6 × 3
Peso Unidades peso_por_unidad
<dbl> <dbl> <dbl>
1 24.9 30 0.830
2 26.3 38 0.691
3 24.9 32 0.778
4 14.0 28 0.501
5 14.4 31 0.464
6 13.8 25 0.551
ggplot(df, aes(x = peso_por_unidad)) +
geom_histogram(binwidth = 0.6, fill = "blue", color = "black") +
labs(title = "Distribución del Peso por Unidad", x = "Peso por Unidad", y = "Frecuencia")
Cantidad de entregas por mes
# Cargar librerías necesarias
library(ggplot2)
library(lubridate)
# Asegurarse de que la columna InicioVisitaPlanificado esté en formato Date
df$InicioVisitaPlanificado <- as.Date(df$InicioVisitaPlanificado, format = "%Y-%m-%d")
df_cantidad_de_entregas_por_dia<-df%>%
group_by(InicioVisitaPlanificado)%>%
summarise(n=n())%>%
mutate(mes = month(InicioVisitaPlanificado))
ggplot(df_cantidad_de_entregas_por_dia, aes(x = InicioVisitaPlanificado, y = n, group = mes, color = as.factor(mes))) +
geom_line(size = 0.8) + # Líneas de cada mes
geom_point(size = 1.5) + # Añadir puntos en las líneas
labs(title = "Cantidad de entregas a lo largo del tiempo por mes",
x = "Fecha de Inicio de Visita Planificado",
y = "Cantidad de entregas",
color = "Mes") + # Añadir etiqueta a la leyenda
theme_minimal() +
theme(panel.grid.major = element_line(color = "gray90")) Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.

Cantidad de entregas por cliente en todos los meses
df$InicioVisitaPlanificado <- as.Date(df$InicioVisitaPlanificado, format = "%Y-%m-%d")
df$cliente<-as.character(df$cliente)
cantidad_entregas_por_cliente<-df%>%
group_by(InicioVisitaPlanificado,cliente)%>%
summarise(n=n())%>%
mutate(mes = month(InicioVisitaPlanificado))
cantidad_entregas_por_cliente# A tibble: 149 × 4
# Groups: InicioVisitaPlanificado [77]
InicioVisitaPlanificado cliente n mes
<date> <chr> <int> <dbl>
1 2024-05-03 20 192 5
2 2024-05-03 70 159 5
3 2024-05-04 20 212 5
4 2024-05-04 70 141 5
5 2024-05-06 20 175 5
6 2024-05-06 70 141 5
7 2024-05-07 20 292 5
8 2024-05-07 70 184 5
9 2024-05-08 20 321 5
10 2024-05-08 70 164 5
# ℹ 139 more rows
ggplot(cantidad_entregas_por_cliente, aes(x =InicioVisitaPlanificado , y = n, color = cliente)) +
geom_line(size = 1) +
geom_point(size = 2.5) +
labs(title = "Cantidad de productos entregados por cliente cada día",
x = "Fecha de Entrega",
y = "Cantidad de Productos Entregados",
color = "Cliente") +
theme_minimal()
Cantidad de entregas por mes
df$mes <- as.factor(df$mes)
entregas_por_mes <- df %>%
group_by(mes) %>%
summarise(cantidad_entregas = n())
ggplot(entregas_por_mes, aes(x = mes, y = cantidad_entregas)) +
geom_bar(stat = "identity", fill = "blue", color = "black") +
labs(title = "Cantidad de Entregas por Mes", x = "Mes", y = "Cantidad de Entregas") +
theme_minimal()
Distribución de entregas en los distintos días de la semana
library(plotly)
library(dplyr)
library(sf)
library(lubridate)
p <- plot_ly()
dias_ordenados <- unique(df.sf$dia_semana)
for (dia in dias_ordenados) {
df_dia <- df.sf[df.sf$dia_semana == dia, ]
coords_dia <- st_coordinates(df_dia)
p <- p %>%
add_trace(
type = 'scattermapbox',
mode = 'markers',
lon = coords_dia[, 1],
lat = coords_dia[, 2],
text = ~paste(df_dia$cliente, "<br>", df_dia$dia_semana),
marker = list(size = 8),
name = as.character(dia),
visible = ifelse(dia == "Domingo", TRUE, FALSE) # Mostrar solo domingo inicialmente
)
}
p <- p %>%
layout(
mapbox = list(
style = 'streets',
zoom = 8,
center = list(lon = mean(coords[, 1]), lat = mean(coords[, 2]))
),
showlegend = TRUE,
updatemenus = list(
list(
type = "buttons",
direction = "down",
buttons = lapply(1:length(dias_ordenados), function(i) {
list(
method = "restyle",
args = list("visible", lapply(1:length(dias_ordenados), function(j) j == i)),
label = dias_ordenados[i]
)
}),
pad = list(r = 10, t = 10),
showactive = TRUE,
x = 1.2,
xanchor = "right",
y = 0.5,
yanchor = "middle",
bgcolor = "#F0F8FF",
bordercolor = "#908090",
borderwidth = 2,
font = list(
family = "Arial",
size = 14,
color = "#333333"
),
activecolor = "#1E90FF"
)
)
) %>%
config(mapboxAccessToken = Sys.getenv("MAPBOX_TOKEN"))
plibrary(plotly)
library(dplyr)
library(sf)
library(lubridate) # Para manejar fechas
# Asegurarse de que la columna 'fecha' esté en formato Date (si no lo está ya)
df$InicioVisitaPlanificado <- as.Date(df$InicioVisitaPlanificado, format = "%Y-%m-%d")
# Agregar una columna con el día de la semana (1 = lunes, 7 = domingo)
df <- df %>%
mutate(dia_semana = wday(InicioVisitaPlanificado, label = TRUE, abbr = FALSE))
# Filtrar y convertir los datos a objeto sf (para las coordenadas)
df.sf <- df %>%
filter(!is.na(longitud) & !is.na(latitud)) %>%
filter(longitud < 0) %>%
filter(latitud < -34.0) %>%
st_as_sf(coords = c("longitud", "latitud"), crs = 4326)
# Extraer las coordenadas
coords <- st_coordinates(df.sf)
# Crear un gráfico interactivo de puntos coloreados por día de la semana
plot_ly() %>%
add_trace(
type = 'scattermapbox',
mode = 'markers',
lon = coords[, 1], # Longitud
lat = coords[, 2], # Latitud
text = ~paste(df.sf$cliente, "<br>", df.sf$dia_semana), # Mostrar cliente y día de la semana al pasar el mouse
marker = list(
size = 8
),
color = ~df.sf$dia_semana, # Colorear por día de la semana de forma discreta
colors = 'Set1' # Usar una paleta de colores discreta como Set1, Set2, etc.
) %>%
layout(
mapbox = list(
style = 'streets',
zoom = 8, # Ajusta el nivel de zoom según tu preferencia
center = list(lon = mean(coords[, 1]), lat = mean(coords[, 2])) # Centrar en las coordenadas promedio
),
showlegend = TRUE # Mostrar la leyenda para identificar los días de la semana
) %>%
config(mapboxAccessToken = Sys.getenv("MAPBOX_TOKEN"))Cluster de Entregas por Localidad
coords <- df_clean %>%
select(latitud, longitud) %>% # Ajustar si los nombres son diferentes
na.omit() # Eliminar filas con valores faltantes
clusters <- kmeans(coords, centers = 3)
df_clean$cluster <- as.factor(clusters$cluster)
ggplot(df_clean, aes(x = longitud, y = latitud, color = cluster)) + # Ajustar nombres si es necesario
geom_point() +
labs(title = "Clusters de Entregas por Localidad") +
theme_minimal()